﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Core;

namespace Silvermoon.Animations
{
    /// <summary>
    /// Provides animations to <see cref="T:Silvermoon.Controls.Control"/>s.
    /// </summary>
    public class Transition
    {
        const int EndValue = 10000;

        /// <summary>
        /// Performs an asynchronous transition.
        /// </summary>
        /// <param name="control">The control to transform.</param>
        /// <param name="mode">The transition mode which can have logical combinations.</param>
        /// <param name="duration">The total duration for the transition. </param>
        /// <param name="startDelay">The start delay before the transition starts.</param>
        /// <param name="onCompleted">An <see cref="T:AnimEventArgs"/> event handler that occurs when the transition is complete. Can be null. </param>
        /// <returns>An <see cref="T:AnimEventArgs"/> class to control and monitor the running animation. </returns>
        public static AnimEventArgs InvokeAsync(Control control, TransitionMode mode, int duration, int startDelay, EventHandler<AnimEventArgs> onCompleted)
        {
            return InvokeAsync(new Control[] { control }, mode, duration, startDelay, onCompleted, null);
        }

        /// <summary>
        /// Performs an asynchronous transition.
        /// </summary>
        /// <param name="control">An enumeration of <see cref="T:Control"/> for which to apply the specified transition.</param>
        /// <param name="mode">The transition mode which can have logical combinations.</param>
        /// <param name="duration">The total duration for the transition. </param>
        /// <param name="startDelay">The start delay before the transition starts.</param>
        /// <param name="onCompleted">An <see cref="T:AnimEventArgs"/> event handler that occurs when the transition is complete. Can be null. </param>
        /// <returns>An <see cref="T:AnimEventArgs"/> class to control and monitor the running animation. </returns>
        public static AnimEventArgs InvokeAsync(IEnumerable<Control> controls, TransitionMode mode, int duration, int startDelay, EventHandler<AnimEventArgs> onCompleted)
        {
            return InvokeAsync(controls, mode, duration, startDelay, onCompleted, null);
        }

        /// <summary>
        /// Performs an asynchronous transition.
        /// </summary>
        /// <param name="control">An enumeration of <see cref="T:Control"/> for which to apply the specified transition.</param>
        /// <param name="mode">The transition mode which can have logical combinations.</param>
        /// <param name="duration">The total duration for the transition. </param>
        /// <param name="startDelay">The start delay before the transition starts.</param>
        /// <param name="onCompleted">An <see cref="T:AnimEventArgs"/> event handler that occurs when the transition is complete. Can be null. </param>
        /// <returns>An <see cref="T:AnimEventArgs"/> class to control and monitor the running animation. </returns>
        public static AnimEventArgs InvokeAsync(IEnumerable<Control> controls, TransitionMode mode, int duration, int startDelay,
            EventHandler<AnimEventArgs> onCompleted,
            EventHandler<AnimEventArgs> onProgress)
        {
            Screen.Current.HideToolTip();
            foreach (Control c in controls)
            {
                c.Visible = true;
                // preload the control to prevent expensive calculation while in transition, which would cause animation to stumble:
                c.PreLoad();
            }

            ControlAnimEventArgs e = new ControlAnimEventArgs(duration, 0, EndValue, startDelay, controls, mode);
            e.Complete += new EventHandler<AnimEventArgs>(OnCompleted);
            if (onCompleted != null) e.Complete += onCompleted;
            e.Transformations = AttachTransformation(controls, mode);
            e.Progress += new EventHandler<AnimEventArgs>(OnProgress);
            if (onProgress != null) e.Progress += onProgress;
            e.InvokeAsync();
            return e;
        }


        /// <summary>
        /// Performs a synchronous transition.
        /// </summary>
        /// <param name="control">An enumeration of <see cref="T:Control"/> for which to apply the specified transition.</param>
        /// <param name="mode">The transition mode which can have logical combinations.</param>
        /// <param name="duration">The total duration for the transition. </param>
        /// <param name="startDelay">The start delay before the transition starts.</param>
        /// <param name="onCompleted">An <see cref="T:AnimEventArgs"/> event handler that occurs when the transition is complete. Can be null. </param>
        public static void Invoke(IEnumerable<Control> controls, TransitionMode mode, int duration, int startDelay, EventHandler<AnimEventArgs> onCompleted)
        {
            Screen.Current.HideToolTip();
            foreach (Control c in controls)
            {
                c.Visible = true;
                // preload the control to prevent expensive calculation while in transition, which would cause animation to stumble:
                c.PreLoad();
            }

            ControlAnimEventArgs e = new ControlAnimEventArgs(duration, 0, EndValue, startDelay, controls, mode);
            e.Complete += new EventHandler<AnimEventArgs>(OnCompleted);
            if (onCompleted != null) e.Complete += onCompleted;
            e.Transformations = AttachTransformation(controls, mode);
            e.Progress += new EventHandler<AnimEventArgs>(OnProgress);
            e.Invoke();
        }

        /// <summary>
        /// Performs an synchronous transition.
        /// </summary>
        /// <param name="control">The control to transform.</param>
        /// <param name="mode">The transition mode which can have logical combinations.</param>
        /// <param name="duration">The total duration for the transition. </param>
        /// <param name="startDelay">The start delay before the transition starts.</param>
        /// <param name="onCompleted">An <see cref="T:AnimEventArgs"/> event handler that occurs when the transition is complete. Can be null. </param>
        public static void Invoke(Control control, TransitionMode mode, int duration, int startDelay, EventHandler<AnimEventArgs> onCompleted)
        {
            Invoke(new Control[] { control }, mode, duration, startDelay, onCompleted);
        }


        static void OnCompleted(object sender, AnimEventArgs e)
        {
            ControlAnimEventArgs ce = (ControlAnimEventArgs)e;
            DetachTransformation(ce.Controls, ce.Transformations);
            bool visible = Check(ce.Mode, TransitionMode.Show);
            foreach (Control c in ce.Controls) c.Visible = visible;
        }



        struct AnimTransforms
        {
            public TranslateTransform TranslateTransform;
            public ScaleTransform ScaleTransfrom;
            public TransparentTransform TransparentTransform;
        }

        static void OnProgress(object sender, AnimEventArgs e)
        {
            ControlAnimEventArgs ce = (ControlAnimEventArgs)e;

            AnimTransforms at = new AnimTransforms();

            foreach (var t in ce.Transformations)
            {
                if (t is TranslateTransform) at.TranslateTransform = (TranslateTransform)t;
                if (t is ScaleTransform) at.ScaleTransfrom = (ScaleTransform)t;
                if (t is TransparentTransform) at.TransparentTransform = (TransparentTransform)t;
            }
            foreach (Control c in ce.Controls)
            {
                ProgressControl(c, at, ce);
            }

            if (e.IsCompleted) DetachTransformation(ce.Controls, ce.Transformations);
        }

        private static void ProgressControl(Control c, AnimTransforms transforms, ControlAnimEventArgs e)
        {
            if (transforms.ScaleTransfrom != null) ZoomTransform(c, transforms, e);
            if (transforms.TranslateTransform != null) SwipeTransform(c, transforms, e);
            if (transforms.TransparentTransform != null) FadeTransform(transforms, e);
            c.Invalidate(InvalidationFlag.Transformation);
        }

        private static void ZoomTransform(Control c, AnimTransforms transforms, ControlAnimEventArgs e)
        {
            TransitionMode mode = e.Mode;
            int value = e.Value;
            int max = e.EndValue;
            bool show = Check(mode, TransitionMode.Show);

            ScaleTransform sc = transforms.ScaleTransfrom;
            float w = Math.Max(1, c.Width);
            float h = Math.Max(1, c.Height);
            if (show)
            {
                if (Check(mode, TransitionMode.StretchHorizontal))
                {
                    sc.X = (1f / w * (c.Width * value / max));
                }
                if (Check(mode, TransitionMode.StretchVertical))
                {
                    sc.Y = (1f / h * (c.Height * value / max));
                }

            }
            else
            {
                if (Check(mode, TransitionMode.StretchHorizontal))
                {
                    sc.X = (1f / w * (c.Width - (c.Width * value / max)));
                }
                if (Check(mode, TransitionMode.StretchVertical))
                {
                    sc.Y = (1f / h * (c.Height - (c.Height * value / max)));
                }
            }
        }

        private static void FadeTransform(AnimTransforms transforms, ControlAnimEventArgs e)
        {
            TransitionMode mode = e.Mode;
            int value = e.Value;
            int max = e.EndValue;
            bool show = Check(mode, TransitionMode.Show);

            if (show)
            {
                transforms.TransparentTransform.Alpha = (255 * value / max);
            }
            else
            {
                transforms.TransparentTransform.Alpha = 255 - (255 * value / max);
            }
        }

        private static void SwipeTransform(Control c, AnimTransforms transforms, ControlAnimEventArgs e)
        {
            TransitionMode mode = e.Mode;
            int value = e.Value;
            int max = e.EndValue;
            bool show = Check(mode, TransitionMode.Show);
            if (show) value = max - value;

            TranslateTransform tt = transforms.TranslateTransform;
            if (Check(mode, TransitionMode.SwipeToLeft))
            {
                int w = c.Width;
                int x = -((value * w) / max);
                tt.OffsetX = (float)x;
            }
            else if (Check(mode, TransitionMode.SwipeToRight))
            {
                int w = c.Width;
                int x = ((value * w) / max);
                tt.OffsetX = (float)x;
            }
            if (Check(mode, TransitionMode.SwipeUp))
            {
                int h = c.Height;
                int y = -((value * h) / max);
                tt.OffsetY = (float)y;
            }
            else if (Check(mode, TransitionMode.SwipeDown))
            {
                int h = c.Height;
                int y = ((value * h) / max);
                tt.OffsetY = (float)y;
            }
        }

        private static bool Check(TransitionMode mode, TransitionMode flag)
        {
            return (mode & flag) != 0;
        }

        private static IEnumerable<Transformation> AttachTransformation(IEnumerable<Control> controls, TransitionMode mode)
        {
            List<Transformation> transformatons = new List<Transformation>();

            if (Check(mode, TransitionMode.SwipeToLeft | TransitionMode.SwipeToRight | TransitionMode.SwipeDown | TransitionMode.SwipeUp))
            {
                transformatons.Add(new TranslateTransform());
            }
            if (Check(mode, TransitionMode.Fade)) transformatons.Add(new TransparentTransform { Alpha = ((mode & TransitionMode.Show) != 0) ? 0 : 255 });
            if (Check(mode, TransitionMode.Zoom)) transformatons.Add(new ScaleTransform());

            foreach (Transformation t in transformatons)
            {
                foreach (IControlAnimation c in controls)
                {
                    c.AnimationTransformations.Add(t);
                }
            }
            return transformatons;
        }

        private static void DetachTransformation(IEnumerable<Control> controls, IEnumerable<Transformation> transformations)
        {
            foreach (IControlAnimation c in controls)
            {
                foreach (Transformation t in transformations)
                {
                    c.AnimationTransformations.Remove(t);
                }
            }
        }
    }

}
